package a.thread;

import a.thread.AsyncTaskWrapper;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.AsyncTask.Status;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.util.Log;

/**
 * 
 * @author The Tran
 *
 */
public class TaskFragment extends Fragment {
    static final String TAG = "TaskFragment";
    public static final String TASK_FRAGMENT_TAG = "TASK_FRAGMENT_TAG";
    public static final int TASK_FRAGMENT_REQUEST_CODE = 0;

    private AsyncTaskWrapper<?, ?, ?> mTask;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // Retain this instance so it isn't destroyed when target Activity and
        // target Fragment change configuration.
        setRetainInstance(true);

        Log.d(TAG, "CREATE TASK_FRAGMENT: " + this);
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "DESTROY TASK_FRAGMENT: " + this);
        super.onDestroy();
    }

    public static boolean hasAttachedTask(FragmentManager fragmentManager) {
        TaskFragment fragment = getRetainedInstance(fragmentManager);
        if (fragment == null) {
            return false;
        }
        return fragment.getTask() != null;
    }

    /**
     * Create new instance of {@link TaskFragment} if it does not exist yet.
     * 
     * @param targetFragment
     * @param fragmentManager
     * @return the new instance or retained instance of {@link TaskFragment}
     */
    private static TaskFragment initInstance(Fragment targetFragment, FragmentManager fragmentManager) {
        // Check to see if we have retained the worker fragment
        TaskFragment taskFragment = (TaskFragment)fragmentManager
                .findFragmentByTag(TaskFragment.TASK_FRAGMENT_TAG);

        if (taskFragment == null) {
            taskFragment = new TaskFragment();
            fragmentManager
                 .beginTransaction()
                 .add(taskFragment, TaskFragment.TASK_FRAGMENT_TAG)
                 .commit();
        }
        // Update the target fragment
        if (targetFragment != null) {
            taskFragment.setTargetFragment(
                    targetFragment, 
                    TaskFragment.TASK_FRAGMENT_REQUEST_CODE);
        }
        return taskFragment;
    }

    /**
     * Attach a task. Called before executing the {@link AsyncTask} and after
     * the target Fragment is created.
     * 
     * @param task
     * @param fragmentManager
     */
    public static void attachTask(AsyncTaskWrapper<?, ?, ?> task, FragmentManager fragmentManager) {
        TaskFragment taskFragment = initInstance(null, fragmentManager);
        taskFragment.setTask(task);
    }

    /**
     * Attach a task. Called before executing the {@link AsyncTask} and after
     * the target Fragment is created.
     * 
     * @param task
     * @param targetFragment
     * @param fragmentManager
     */
    public static void attachTask(AsyncTaskWrapper<?, ?, ?> task, Fragment targetFragment, FragmentManager fragmentManager) {
        TaskFragment taskFragment = initInstance(targetFragment, fragmentManager);
        taskFragment.setTask(task);
    }

    /**
     * Detach current task. Should be called when the task is finished or
     * cancelled
     * 
     * @param fragmentManager
     */
    public static void detachTask(FragmentManager fragmentManager) {
        TaskFragment taskFragment = getRetainedInstance(fragmentManager);
        if (taskFragment == null) {
            Log.d(TAG, "detachTask: Do not have the retained Fragment instance");
            return;
        }

        AsyncTaskWrapper<?, ?, ?> task = taskFragment.getTask();
        if (task != null && task.getStatus() != Status.FINISHED) {
            task.cancel(true);
        }
        taskFragment.setTask(null);

        fragmentManager
            .beginTransaction()
            .remove(taskFragment)
            .commit();
    }

    /**
     * Must be called in the target Fragment's <code><b>onCreate()</b></code>
     * method. Attach current task to a new activity
     * 
     * @param newActivity
     * @param fragmentManager
     * @return null if there is no task currently attached to
     *         {@link TaskFragment}
     */
    public static AsyncTaskWrapper<?, ?, ?> attachTaskToActivity(Activity newActivity, FragmentManager fragmentManager) {
        TaskFragment taskFragment = getRetainedInstance(null, fragmentManager);
        if (taskFragment == null) {
            Log.d(TAG, "attachTaskToActivity: Do not have the retained Fragment instance");
            return null;
        }
        AsyncTaskWrapper<?, ?, ?> task = taskFragment.getTask();
        if (task != null) {
            Log.d(TAG, "Do attach " + task.getTaskName() + " to new activity " + newActivity);
            task.reattach(newActivity);
        }
        return taskFragment.getTask();
    }

    /**
     * Must be called in the target Fragment's <code><b>onCreate()</b></code>
     * method. Attach current task to a new activity
     * 
     * @param newActivity
     * @param targetFragment
     * @param fragmentManager
     * @return null if there is no task currently attached to
     *         {@link TaskFragment}
     */
    public static AsyncTaskWrapper<?, ?, ?> attachTaskToActivity(Activity newActivity, Fragment targetFragment, FragmentManager fragmentManager) {
        TaskFragment taskFragment = getRetainedInstance(targetFragment, fragmentManager);
        if (taskFragment == null) {
            Log.d(TAG, "attachTaskToActivity: Do not have the retained Fragment instance");
            return null;
        }
        AsyncTaskWrapper<?, ?, ?> task = taskFragment.getTask();
        if (task != null) {
            Log.d(TAG, "Do attach " + task.getTaskName() + " to new activity " + newActivity);
            task.reattach(newActivity);
        }
        return taskFragment.getTask();
    }

    /**
     * Must be called in the target Fragment's <b><code>onDestroy()</code></b>
     * method. Detach current task from an activity.
     * 
     * @param fragmentManager
     */
    public static void detachTaskFromActivity(FragmentManager fragmentManager) {
        TaskFragment taskFragment = getRetainedInstance(fragmentManager);
        if (taskFragment == null) {
            Log.d(TAG, "detachTaskToActivity: Do not have the retained Fragment instance");
            return;
        }

        AsyncTaskWrapper<?, ?, ?> task = taskFragment.getTask();
        if (task != null) {
            Log.i(TAG, "Do detach " + task.getTaskName() + " from old activity");
            task.detach();
        }
    }

    /**
     * Get the retained {@link TaskFragment} instance if any.
     * If there is retained instance, update its target fragment.
     * @param targetFragment
     * @param fragmentManager
     * @return null if it does not have the retained instance of
     *         {@link TaskFragment}
     */
    private static TaskFragment getRetainedInstance(Fragment targetFragment, FragmentManager fragmentManager) {
        TaskFragment taskFragment = (TaskFragment)fragmentManager
                .findFragmentByTag(TaskFragment.TASK_FRAGMENT_TAG);

        if (taskFragment == null) {
            return null;
        }

        // Update the target fragment so it goes to this fragment instead of the old one.
        // This will also allow the GC to reclaim the old target Fragment, which the TaskFragment
        // keeps a reference to. Note that I looked in the code and setTargetFragment() doesn't
        // use weak references. To be sure you aren't leaking, you may wish to make your own
        // setTargetFragment() which does.
        if (targetFragment != null) {
            taskFragment.setTargetFragment(
                    targetFragment, 
                    TaskFragment.TASK_FRAGMENT_REQUEST_CODE);
        }
        return taskFragment;
    }

    /**
     * Get the retained {@link TaskFragment} instance if any.
     * 
     * @param targetFragment
     * @param fragmentManager
     * @return null if it does not have the retained instance of
     *         {@link TaskFragment}
     */
    private static TaskFragment getRetainedInstance(FragmentManager fragmentManager) {
        TaskFragment taskFragment = (TaskFragment)fragmentManager
                .findFragmentByTag(TaskFragment.TASK_FRAGMENT_TAG);
        return taskFragment;
    }

    private void setTask(AsyncTaskWrapper<?, ?, ?> task) {
        mTask = task;
    }

    private AsyncTaskWrapper<?, ?, ?> getTask() {
        return mTask;
    }
}
